#include <cstddef>
#include <cstring>
#include <ctime>

#include "cgi.h"
#include "css.h"
#include "exception.h"
#include "favicon.h"
#include "ByteString.h"
#include "SysLog.h"
#include "util.h"
#include "WikiStorage.h"
#include "wiki.h"

/// Current release-number.
#define VERSION "0.271812"

/// Charset of wikipages.
#define CHARSET "utf-8"

/// Maximum page size.
#define MAX_PAGE_SIZE (64*1024)

#define WIKI_STORAGE_LOCATION	"/home/www/dwikiroot"
//#define WIKI_STORAGE_LOCATION	"../root"
//#define WIKI_STORAGE_LOCATION	"/tmp/storage"

WikiStorage_t * wiki_storage;

ByteString_t server_url;
ByteString_t script_name;

void build_header(const ByteString_t & _path_info) {

		const char PAGEHEADER_STAGE1[] = \
				"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">" \
				"<html xmlns=\"http://www.w3.org/1999/xhtml\">" \
				"<head>" \
				"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=" CHARSET "\" />"
				"<link rel=\"SHORTCUT ICON\" href=\""; // script_name / favicon.ico

		const char PAGEHEADER_STAGE2[] = \
				"\" /><link media=\"all\" href=\""; // script_name / styles.css
				
		const char PAGEHEADER_STAGE3[] = \
				"\" rel=\"stylesheet\" type=\"text/css\" /><title>"; // name

		const char PAGEHEADER_STAGE4[] = \
				"</title></head><body>" \
				"<div id=\"header\"><table border=\"0\" width=\"100%%\">" \
				"<tr><td align=\"left\"><strong>"; // name

		const char PAGEHEADER_STAGE5[] = \
				"</strong>";

		const char EDIT_STAGE1[] = \
				" ( <a href='"; // script_name path_info ?edit

		const char EDIT_STAGE2[] = \
				"' title=\"Edit this wiki page contents. [alt-j]\" accesskey=\"j\">Edit</a> ) ";
		
		const char PAGEHEADER_STAGE6[] = \
				"</td>" \
				"<td align=\"right\">";

		const char UP_STAGE1[] = \
				"<a href=\""; // script_name / up_page

		const char UP_STAGE2[] = \
				"\" title=\"Visit previous level page. [alt-u]\" accesskey=\"u\">Up</a> | ";
		
		const char PAGEHEADER_STAGE7[] = \
				"<a href=\""; // script_name /WikiHome

		const char PAGEHEADER_STAGE8[] = \
				"\" title=\"Visit Wiki home page. [alt-z]\" accesskey=\"z\">Home</a> | " \
				"<a href=\""; // script_name ?new

		const char PAGEHEADER_STAGE9[] = \
				"\" title=\"Create a new wiki page by title. [alt-c]\" accesskey=\"c\">New</a> | " \
				"<a href=\""; // script_name /changes

		const char PAGEHEADER_STAGE10[] = \
				"\" title=\"List recent changes in wiki. [alt-r]\" accesskey=\"r\">Changes</a> | " \
				"<a href=\""; // script_name /WikiHelp

		const char PAGEHEADER_STAGE11[] = \
				"\" title=\"Get help on wiki usage and formatting.\">Help</a>" \
				"</td></tr></table></form></div><div id=\"wikidata\">";

		ByteString_t name(_path_info.array() + 1, _path_info.length() - 1);

		const bool is_edit = ( NULL == cgi_request::get_param_string("edit") && NULL == cgi_request::get_param_string("new") );
		const bool is_up = ( (size_t)-1 != name.find_rchr('/') );
		
		cgi_responde::set_content_type(mime_text_html);
		
		cgi_responde::append(PAGEHEADER_STAGE1);
		cgi_responde::append(script_name);
		cgi_responde::append("/favicon.ico");
		
		cgi_responde::append(PAGEHEADER_STAGE2);
		cgi_responde::append(script_name);
		cgi_responde::append("/styles");

		cgi_responde::append(PAGEHEADER_STAGE3);
		cgi_responde::append(name);

		cgi_responde::append(PAGEHEADER_STAGE4);
		cgi_responde::append(name);

		cgi_responde::append(PAGEHEADER_STAGE5);

		if( is_edit ) {
				cgi_responde::append(EDIT_STAGE1);
				cgi_responde::append(script_name);
				cgi_responde::append(_path_info);
				cgi_responde::append("?edit");
				cgi_responde::append(EDIT_STAGE2);
		}
		
		cgi_responde::append(PAGEHEADER_STAGE6);

		if( is_up ) {
				cgi_responde::append(UP_STAGE1);
				cgi_responde::append(script_name);
				cgi_responde::append(_path_info.array(), _path_info.find_rchr('/'));
				cgi_responde::append(UP_STAGE2);
		}

		cgi_responde::append(PAGEHEADER_STAGE7);
		cgi_responde::append(script_name);
		cgi_responde::append("/WikiHome");

		cgi_responde::append(PAGEHEADER_STAGE8);
		cgi_responde::append(script_name);
		cgi_responde::append("?new");
		
		cgi_responde::append(PAGEHEADER_STAGE9);
		cgi_responde::append(script_name);
		cgi_responde::append("/changes");

		cgi_responde::append(PAGEHEADER_STAGE10);
		cgi_responde::append(script_name);
		cgi_responde::append("/WikiHelp");

		cgi_responde::append(PAGEHEADER_STAGE11);
}

void build_footer() {
		const char PAGEFOOTER[] = "</div><div id=\"footer\">DWiki, Version: " VERSION "</div></body></html>";

		cgi_responde::append(PAGEFOOTER);
}

void build_wiki(const ByteString_t & _wiki) {
		char page[ MAX_PAGE_SIZE ];
		size_t page_size;

		page_size = wiki_parser(_wiki.array(), _wiki.length(), page, sizeof(page));

		cgi_responde::append(page, page_size);
}

void build_editpage(const ByteString_t & _path_info, const ByteString_t & _wiki) {
		const char EDITFORM_STAGE1[] = \
				"<form method=POST action='"; // script_name

		const char EDITFORM_STAGE2[] = \
				"' name=\"editform\"><textarea name=\"edit\" rows=\"20\" cols=\"80\" warp=\"virtual\">"; // wiki

		const char EDITFORM_STAGE3[] = \
				"</textarea><br/><input type=\"submit\" value=\"Save\" title=\"[alt-k]\" accesskey=\"k\"></form>";

		cgi_responde::append(EDITFORM_STAGE1);
		cgi_responde::append(script_name);
		cgi_responde::append(_path_info);
		cgi_responde::append(EDITFORM_STAGE2);
		cgi_responde::append(_wiki);
		cgi_responde::append(EDITFORM_STAGE3);
}

void build_newpage() {
		const char CREATEFORM_STAGE1[] = \
				"<form method=POST action=\""; // script_name

		const char CREATEFORM_STAGE2[] = \
				"\"><input type=\"text\" maxlength=\"127\" name=\"new\" /><p>" \
				"<input type=\"submit\" value=\"Create\" /></p></form>";

		cgi_responde::append(CREATEFORM_STAGE1);
		cgi_responde::append(script_name);
		cgi_responde::append(CREATEFORM_STAGE2);
}

void handler_redirect_homepage() {
		ByteString_t t;
		t.append(server_url).append(script_name).append("/WikiHome").append('\0');

		cgi_responde::set_status(cgi_responde::status_301_moved_permanently);
		cgi_responde::set_location(t.array());
}

void handler_favicon() {
		cgi_responde::set_content_type(mime_image_ico);
		cgi_responde::append((const char*)FAVICON_DATA, FAVICON_SIZE);
}

void handler_styles() {
		cgi_responde::set_content_type(mime_text_css);
		cgi_responde::append(CSS_DATA, CSS_SIZE);
}

void add_changes(const ByteString_t & _name) {
		ByteString_t wiki;
		ByteString_t wiki_new;

		wiki_storage->get("changes", &wiki);

		wiki_new.append("{\\ref ").append(_name).append("} ").append(util_ctime()).append("\n\n");

		if( wiki.length() + wiki_new.length() > MAX_PAGE_SIZE )
				wiki_storage->set("changes.old", &wiki);
		else
				wiki_new.append(wiki);

		wiki_storage->set("changes", &wiki_new);
}

void handler_edit(const ByteString_t & _path_info) {
		const cgi_request::RequestParam_t * pedit = cgi_request::get_param_string("edit");
		
		ByteString_t name(_path_info.array() + 1, _path_info.length() - 1);
		name.append('\0');	// @@
		
		if( NULL == pedit->value ) {
				ByteString_t wiki;

				wiki_storage->get(name.array(), &wiki);

				build_header(_path_info);
				build_editpage(_path_info, wiki);
				build_footer();
				
		} else {
				char * tmp = util_htmlize(pedit->value, pedit->value_size);
				if( NULL == tmp )
						throw std::bad_alloc();

				ByteString_t wiki;
				try {
						wiki.append(tmp);
				} catch(...) {
						delete [] tmp;
						throw;
				}
				delete [] tmp;

				name.resize(name.length() - 1);		// @@
				wiki_storage->set(name.array(), &wiki);

				add_changes(name);

				ByteString_t t;
				t.append( server_url ).append( script_name ).append( cgi_request::get_path_info() ).append( '\0' );

				cgi_responde::set_status(cgi_responde::status_302_found);
				cgi_responde::set_location(t.array());
		}
}

void handler_new() {
		const cgi_request::RequestParam_t * pnew = cgi_request::get_param_string("new");
		
		if( NULL == pnew )
				throw std::logic_error("handler_new(): There is ?new undefined.");

		if( NULL == pnew->value ) {
				build_header("/new page");
				build_newpage();
				build_footer();

		} else if( ! wiki_storage->check_key(pnew->value, pnew->value_size) ) {
				throw std::invalid_argument("handler_new(): There is incorrect ?new value.");

		} else {
				ByteString_t t;
				t.append( server_url ).append( script_name ).append( '/' ).append( pnew->value, pnew->value_size ).append("?edit").append('\0');
				cgi_responde::set_status(cgi_responde::status_302_found);
				cgi_responde::set_location(t.array());
		}
}

void handler_page(const ByteString_t & _path_info) {
		ByteString_t name(_path_info.array() + 1, _path_info.length() - 1);
		name.append('\0');
		ByteString_t wiki;

		wiki_storage->get(name.array(), &wiki);

		build_header(_path_info);
		build_wiki(wiki);
		build_footer();
}

void build() {
		const char * request_method = cgi_request::get_request_method();
		
		if( !strcmp("GET", request_method) || !strcmp("POST", request_method) || !strcmp("PUT", request_method) ) {

				if( NULL != cgi_request::get_param_string("new") ) {
						handler_new();
						return;
				}

				const char * t_path_info = cgi_request::get_path_info();

				if( NULL == t_path_info || strlen(t_path_info) <= 1 ) {
						handler_redirect_homepage();
						return;
				}

				const char * tmp = util_htmlize(t_path_info, strlen(t_path_info));
				if( NULL == tmp )
						throw std::bad_alloc();
				ByteString_t path_info;
				try {
						path_info.append(tmp);
				} catch(...) {
						delete [] tmp;
						throw;
				}
				delete [] tmp;

				if( strlen("/favicon.ico") == path_info.length() &&	!memcmp("/favicon.ico", path_info.array(), path_info.length())  ) {
						handler_favicon();
						return;
				}

				if( strlen("/styles") == path_info.length() &&!memcmp("/styles", path_info.array(), path_info.length()) ) {
						handler_styles();
						return;
				}

				if( NULL != cgi_request::get_param_string("edit") ) {
						handler_edit(path_info);
						return;
				}

				handler_page(path_info);

				return;
				
		} else {
				cgi_responde::set_status( cgi_responde::status_501_not_implemented );
				cgi_responde::set_content_type( mime_text_plain );
				cgi_responde::append( cgi_responde::status_501_not_implemented );
				return;
		}
}

int main(int, char ** argv) {
		const long double clock1 = clock();
		
		SysLog_t _log(argv[ 0 ]);
		log = &_log;

		try {
				wiki_storage = new WikiStorage_t( WIKI_STORAGE_LOCATION );

		} catch( std::bad_alloc ex ) {
				log->alert("%s", "Memory allocation faild.");
				cgi_responde::send_extra(cgi_responde::status_500_internal_server_error);
				return -1;

		} catch( std::io_error ex ) {
				log->alert("%s: %s", "InOut error occured", ex.what());
				cgi_responde::send_extra(cgi_responde::status_500_internal_server_error);
				return -1;				

		} catch( std::invalid_argument ex ) {
				log->alert("%s: %s", "Wrong storage configuration", ex.what());
				cgi_responde::send_extra(cgi_responde::status_500_internal_server_error);
				return -1;

		} catch( ... ) {
				log->emergency("%s", "main(): unknown exception.");
				cgi_responde::send_extra(cgi_responde::status_500_internal_server_error);
				return -1;
		}

		try {
				cgi_request::construct( 0 );
				cgi_responde::construct();

				if( strncmp("HTTP", cgi_request::get_server_protocol(), 4) )
						throw std::invalid_argument("There is not supported server protocol.");

				script_name.append(cgi_request::get_script_name());
				server_url.append("http://");
				server_url.append(cgi_request::get_server_name());
				
				if( 80 != atoi(cgi_request::get_server_port()) )
						server_url.append(':').append(cgi_request::get_server_port());

		} catch( std::bad_alloc ex ) {
				log->alert("%s", "Memory allocation faild.");
				cgi_responde::send_extra(cgi_responde::status_500_internal_server_error);
				return -1;

		} catch( std::invalid_argument ex ) {
				log->emergency("%s: %s", "Wrong script running", ex.what());
				cgi_responde::send_extra(cgi_responde::status_500_internal_server_error);
				return -1;

		} catch( std::io_error ex ) {
				log->alert("%s: %s", "InOut error occured", ex.what());
				cgi_responde::send_extra(cgi_responde::status_500_internal_server_error);
				return -1;				

		} catch( std::length_error ex ) {
				log->notice("%s: %s", "Too lagre request", ex.what());
				cgi_responde::send_extra(cgi_responde::status_413_request_entity_too_large);
				return -1;

		} catch( std::logic_error ex ) {
				log->emergency("%s", ex.what());
				cgi_responde::send_extra(cgi_responde::status_500_internal_server_error);
				return -1;

		} catch( ... ) {
				log->emergency("%s", "main(): unknown exception.");
				cgi_responde::send_extra(cgi_responde::status_500_internal_server_error);
				return -1;
		}

		try {
#ifndef NDEBUG
				{
						const char * gateway_interface = cgi_request::get_gateway_interface();
						const char * script_name = cgi_request::get_script_name();
						const char * path_info = cgi_request::get_path_info();
						const char * request_method = cgi_request::get_request_method();

						log->debug("%s", util_ctime());
						log->debug("### connection ###");
						log->debug("# cgi input");
						log->debug("  gateway interface: %s", gateway_interface);
						log->debug("  script_name: %s", script_name);
						log->debug("  path_info: %s", path_info ? path_info : "");
						log->debug("  request_method: %s", request_method);
						log->flush();
				}
#endif
				build();
				
#ifndef NDEBUG
				{
						const char * status = cgi_responde::get_status();
						const char * location = cgi_responde::get_location();
						const char * content_type = cgi_responde::get_content_type();

						log->debug("# cgi output");
						log->debug("  status: %s", status ? status : "");
						log->debug("  location: %s", location ? location : "");
						log->debug("  content-type: %s", content_type ? content_type : "");
						log->flush();
				}
#endif

				cgi_responde::send();

		} catch( std::bad_alloc ex ) {
				log->alert("%s", "Memory allocation faild.");
				cgi_responde::send_extra(cgi_responde::status_500_internal_server_error);
				return -1;

		} catch( std::io_error ex ) {
				log->alert("%s: %s", "InOut error occured", ex.what());
				cgi_responde::send_extra(cgi_responde::status_500_internal_server_error);
				return -1;

		} catch( const std::invalid_argument ex ) {
				log->warning("%s: %s", "Wrong request", ex.what());
				cgi_responde::send_extra(cgi_responde::status_400_bad_request);
				return -1;

		} catch( const std::out_of_range ex ) {
				log->emergency("%s: %s", "Internal overful", ex.what());
				cgi_responde::send_extra(cgi_responde::status_500_internal_server_error);
				return -1;

		} catch( std::logic_error ex ) {
				log->emergency("%s: %s", "Incorrect arhitechure", ex.what());
				cgi_responde::send_extra(cgi_responde::status_500_internal_server_error);
				return -1;

		} catch( ... ) {
				log->emergency("%s", "main(): unknown exception.");
				cgi_responde::send_extra(cgi_responde::status_500_internal_server_error);
				return -1;
		}

		cgi_request::destruct();
		cgi_responde::destruct();
		
		delete wiki_storage;

		const long double clock2 = clock();

		util_printf("\r\n<!-- clock: %.3Lf -->", (clock2 - clock1) / CLOCKS_PER_SEC);

		return 0;
}
